 /*
 * InputModule.h
 *
 * Created 11/30/2008 By Johnny Huynh
 *
 * Version 00.00.01 11/30/2008
 *
 * Copyright Information:
 * All content copyright © 2008 Johnny Huynh. All rights reserved.
 */
 
 // InputModule.h handles all of the input (e.g. mouse, keyboard) interactions

 #ifndef INPUT_MODULE_H
 #define INPUT_MODULE_H

 #include <iostream>
 
 #include "OpenGL_Headers.h"
 #include "GL_MobileObject.h"
 
 #ifndef null
 #define null 0
 #endif // null
 
 #ifndef VIEW_ANGLE
 #define VIEW_ANGLE 90
 #endif // VIEW_ANGLE
 
 // Tracks if in GLUT game mode
 static GLboolean isGameMode = 0x0;
 
 // Tracks the mouse pointer positions
 static GLint mousePosition[2] = { 0x0, 0x0 };
 
 // Tracks if the mouse movement is valid (on screen/window) or not
 static GLboolean isValidMouseMovement = 0x0;
 
 // Used in Convex Hull for rendering the construction stages
 //static GLint ch_counter = 0x0;
 //static GLboolean showM( false );
 
 static GL_MobileObjectf* mobj_Ptr = null;
 static GL_MobileObjectf* box;
 static GL_MobileObjectf* concave;
 static GL_MobileObjectf* convex;
 static GLint OBBTree_depth_level = 0;
 static GLboolean hasFiredHitBox = true;
 static GLfloat horse_sphere_radius = 1.0f;
 
 inline void applyKeyboardControl();
 void SpecialKeys( int key, int x, int y );
 void KeyboardHandler( unsigned char key, int x, int y );
 void KeyboardUpHandler( unsigned char key, int x, int y );
 void WindowEntry( int state );
 void MouseMovePassive( int x, int y );
 void MouseClickHandler(int button, int state, int x, int y);
 
 // The unit for movement
 #define MOVEMENT_UNIT 0.5f
 
 // ANGLE is measured in radians
 #define ANGLE 0.015f
 #define MAX_ANGLE_MOD 0x7
 #define KEYBOARD_ANGLE 0.07f*global::speed_factor
 
 // Movement speed measured in units
 #define MOVEMENT_SPEED 0.6f*global::speed_factor
 // Rotation speed measured in radians
 #define ROTATION_SPEED 0.07f*global::speed_factor
 
 // Move GL_MobileObject
 #define maintainMobj()                                                                     \
    if ( mobj_Ptr != null )                                                                 \
    {                                                                                       \
        const Vector3f camFace = cam.getFaceAxis();                                         \
        const Vector3f camLocation = cam.getPosition();                                     \
        mobj_Ptr->setPosition( camLocation.x + 4*camFace.x, camLocation.y + 4*camFace.y,    \
                                                camLocation.z + 4*camFace.z );              \
    }
 
 namespace InputButton
 {
    bool moveForward( false );
    bool moveBackward( false );
    bool moveLeft( false );
    bool moveRight( false );
    bool rotateLeft( false );
    bool rotateRight( false );

	bool projectileRotateUp( false );
	bool projectileRotateDown( false );
	bool projectileRotateLeft( false );
	bool projectileRotateRight( false );
    
    bool prioritizeMoveForward( true ); // else prioritize move backward
    bool prioritizeMoveLeft( true ); // else prioritize move right
    bool prioritizeRotateLeft( true ); // else prioritize rotate right

	bool isLightZeroOn( true );
 }
 static GLfloat mouseMoveX( 0.0f );
 static GLfloat mouseMoveY( 0.0f );
 
 void initializeMobj()
 {
     std::vector< GL_Trianglef > trianglesConcave;
     std::vector< GL_Trianglef > trianglesConvex;
     Vector3f vecA( 0.0f, 1.0f, -2.0f );
     Vector3f vecB( -1.0f, 2.0f, 2.0f );
     Vector3f vecC( 0.0f, 2.0f, 0.0f );
     Vector3f vecD( 1.0f, 2.0f, 2.0f );
     Vector3f vecE( 0.0f, 0.0f, 0.0f );

     Vector3f vecX( 1.0f, 0.0f, 0.0f ); // bottom-right
     Vector3f vecY( -2.0f, 1.0f, -3.0f ); // bottom-left
     Vector3f vecZ( 0.5f, 3.0f, -1.2f ); // top

     GL_Trianglef triW( vecE, vecY, vecX ); // bottom face
     GL_Trianglef triX( vecE, vecX, vecZ ); // right face
     GL_Trianglef triY( vecE, vecZ, vecY ); // left face
     GL_Trianglef triZ( vecY, vecZ, vecX ); // back face

     GL_Trianglef triA( vecA, vecB, vecC );
     GL_Trianglef triB( vecC, vecD, vecA );
     GL_Trianglef triC( vecB, vecE, vecC );
     GL_Trianglef triD( vecC, vecE, vecD );
     GL_Trianglef triE( vecA, vecD, vecE );
     GL_Trianglef triF( vecE, vecB, vecA );

     trianglesConcave.push_back( triA );
     trianglesConcave.push_back( triB );
     trianglesConcave.push_back( triC );
     trianglesConcave.push_back( triD );
     trianglesConcave.push_back( triE );
     trianglesConcave.push_back( triF );
     trianglesConvex.push_back( triW );
     trianglesConvex.push_back( triX );
     trianglesConvex.push_back( triY );
     trianglesConvex.push_back( triZ );
     
     concave = new GL_MobileObjectf( trianglesConcave );
     convex = new GL_MobileObjectf( trianglesConvex );
     
     std::vector< GL_Trianglef > trianglesBox;
     Vector3f vecF( -2.0f, -1.5f, -2.5f ); // bottom-back-left
     Vector3f vecG( 2.0f, -1.5f, -2.5f ); // bottom-back-right
     Vector3f vecH( -2.0f, 1.5f, -2.5f ); // top-back-left
     Vector3f vecI( 2.0f, 1.5f, -2.5f ); // top-back-right

     Vector3f vecJ( -2.0f, -1.5f, 2.5f ); // bottom-front-left
     Vector3f vecK( 2.0f, -1.5f, 2.5f ); // bottom-front-right
     Vector3f vecL( -2.0f, 1.5f, 2.5f ); // top-front-left
     Vector3f vecM( 2.0f, 1.5f, 2.5f ); // top-front-right

     GL_Trianglef triM( vecF, vecH, vecI ); // back-top
     GL_Trianglef triN( vecI, vecG, vecF ); // back-bottom
     
     GL_Trianglef triO( vecM, vecL, vecJ ); // front-top
     GL_Trianglef triP( vecJ, vecK, vecM ); // front-bottom
     
     GL_Trianglef triQ( vecL, vecH, vecF ); // left-top
     GL_Trianglef triR( vecF, vecJ, vecL ); // left-bottom
     
     trianglesBox.push_back( triM );
     trianglesBox.push_back( triN );
     trianglesBox.push_back( triO );
     trianglesBox.push_back( triP );
     trianglesBox.push_back( triQ );
     trianglesBox.push_back( triR );
     
     box = new GL_MobileObjectf( trianglesBox );
 }
 
 void SpecialKeysDown( int key, int x, int y )
 {
    switch (key)
	 {
	 case GLUT_KEY_UP:
		InputButton::projectileRotateUp = true;
		break;
	 case GLUT_KEY_DOWN:
		InputButton::projectileRotateDown = true;
		break;
	 case GLUT_KEY_LEFT:
		InputButton::projectileRotateLeft = true;
		break;
	 case GLUT_KEY_RIGHT:
		InputButton::projectileRotateRight = true;
		break;
	 default:
		 break;
	 }
 }

 void SpecialKeysUp( int key, int x, int y )
 {
	 switch (key)
	 {
	 case GLUT_KEY_UP:
		InputButton::projectileRotateUp = false;
		break;
	 case GLUT_KEY_DOWN:
		InputButton::projectileRotateDown = false;
		break;
	 case GLUT_KEY_LEFT:
		InputButton::projectileRotateLeft = false;
		break;
	 case GLUT_KEY_RIGHT:
		InputButton::projectileRotateRight = false;
		break;
	 default:
		 break;
	 }
 }

 void KeyboardHandler( unsigned char key, int x, int y )
 {
    switch( key )
    {
      case  0x1B:
      case  't':
      case  'T':
        exit(0);
        break; 
      case 'w':
      case 'W':
        InputButton::moveForward = true;
        if ( !InputButton::prioritizeMoveForward )
            InputButton::prioritizeMoveForward = true;
        break;
      case 's':
      case 'S':
        InputButton::moveBackward = true;
        if ( InputButton::prioritizeMoveForward )
            InputButton::prioritizeMoveForward = false;
        break;
      case 'a':
      case 'A':
        InputButton::moveLeft = true;
        if ( !InputButton::prioritizeMoveLeft )
            InputButton::prioritizeMoveLeft = true;
        break;
      case 'd':
      case 'D':
        InputButton::moveRight = true;
        if ( InputButton::prioritizeMoveLeft )
            InputButton::prioritizeMoveLeft = false;
        break;
      case 'q':
      case 'Q':
        InputButton::rotateLeft = true;
        if ( !InputButton::prioritizeRotateLeft )
            InputButton::prioritizeRotateLeft = true;
        break;
      case 'e':
      case 'E':
        InputButton::rotateRight = true;
        if ( InputButton::prioritizeRotateLeft )
            InputButton::prioritizeRotateLeft = false;
        break;
	  case 'f':
	  case 'F':
		if ( InputButton::isLightZeroOn )
		{
			glDisable(GL_LIGHT0);
			InputButton::isLightZeroOn ^= 0x1;
		}
		else
		{
			glEnable(GL_LIGHT0);
			InputButton::isLightZeroOn ^= 0x1;
		}
	    break;
      case 'g':
      case 'G':
        if ( isGameMode )
        {
            glutLeaveGameMode();
            isGameMode = false;
            
            enterWindowMode();
        }
        else // !isGameMode
        {
            enterFullScreenMode();
            isGameMode = true;
        }
        break;
      case 'c':
      case 'C':
        if ( hasFiredHitBox )
        {
            hasFiredHitBox = false;
            
            mobj_Ptr = new GL_MobileObjectf( *concave );
            mobj_Ptr->setOrientation( cam.getOrientation() );
            maintainMobj();
            objects.push_back( mobj_Ptr );
        }
        break;
      case 'p':
      case 'P':
        PAUSE ^= 0x1;
        break;
      case '=':
      case '+':
        global::time += 0.1f;
        if ( global::time > 10.0f )
            global::time = 10.0f;
        break;
      case '-':
      case '_':
        global::time -= 0.1f;
        if ( global::time < 0.005f )
            global::time = 0.005f;
        break;
      case 'M':
      case 'm':
        ++OBBTree_depth_level;
        break;
      case 'N':
      case 'n':
        --OBBTree_depth_level;
        break;
      case 'j':
      case 'J':
        global::enableBoundingSphere = !global::enableBoundingSphere;
        break;
      case 'k':
      case 'K':
        global::enableAABB = !global::enableAABB;
        break;
      case 'l':
      case 'L':
        global::enableConvexHull = !global::enableConvexHull;
        break;
      case 'i':
      case 'I':
        global::testCollision = !global::testCollision;
        break;
	  case 'v':
	  case 'V':
		  horse_sphere_radius += 0.5f;
		  break;
	  case 'b':
	  case 'B':
		  if ( horse_sphere_radius > 0.0f )
			horse_sphere_radius -= 0.5f;
		  break;
      /*case 'u':
      case 'U':
        ++ch_counter;
        break;
      case 'i':
      case 'I':
        --ch_counter;
        break;
      case 'o':
      case 'O':
        showM ^= 0x1;
        break;*/
      case 'h':
      case 'H':
        printf("\tPress ESC to quit\n");
        break;
      default:
        break;
    }
    //glutPostRedisplay();
 }
 
 void KeyboardUpHandler( unsigned char key, int x, int y )
 {
    switch( key )
    {
      case 'w':
      case 'W':
        InputButton::moveForward = false;
        break;
      case 's':
      case 'S':
        InputButton::moveBackward = false;
        break;
      case 'a':
      case 'A':
        InputButton::moveLeft = false;
        break;
      case 'd':
      case 'D':
        InputButton::moveRight = false;
        break;
      case 'q':
      case 'Q':
        InputButton::rotateLeft = false;
        break;
      case 'e':
      case 'E':
        InputButton::rotateRight = false;
        break;
      default:
        break;
    }
 }
 
 inline void applyKeyboardControl()
 {
    Vector3f direction( cam.getFaceAxis() );
    
    GLfloat m( (direction.x*direction.x) + (direction.z*direction.z) );
    
    GLfloat dir_x( direction.x/m ); // negative because using geographic coordinate system (x, -z, y)
    GLfloat dir_z( direction.z/m );
    
    GLfloat displacement_x( 0.0f );
    GLfloat displacement_z( 0.0f );
    
    GLfloat globalRotateLeftAngle( 0.0f );
    
    if ( InputButton::moveForward  )
    {
        if ( !InputButton::moveBackward || InputButton::prioritizeMoveForward )
        {
            displacement_x += dir_x;
            displacement_z += dir_z;
        }
        else // InputButton::moveBackward && !InputButton::prioritizeMoveForward
        {
            displacement_x -= dir_x;
            displacement_z -= dir_z;
        }
    }
    else if ( InputButton::moveBackward && !InputButton::moveForward )
    {
        displacement_x -= dir_x;
        displacement_z -= dir_z;
    }
    
    // For left and right movement
    // Rotate the direction 90 degrees
    // crossProduct( (0, 1, 0), (dir_x, 0, dir_z) )
    // = (dir_z, 0, -dir_x )
    //GLfloat temp( dir_x );
    //dir_x = dir_z;
    //dir_z = -temp;
    
    if ( InputButton::moveLeft )
    {
        if ( !InputButton::moveRight || InputButton::prioritizeMoveLeft )
        {
            //displacement_x += dir_x;
            //displacement_z += dir_z;
            displacement_x += dir_z;
            displacement_z -= dir_x;
        }
        else // InputButton::moveRight && !InputButton::prioritizeMoveLeft
        {
            //displacement_x -= dir_x;
            //displacement_z -= dir_z;
            displacement_x -= dir_z;
            displacement_z += dir_x;
        }
    }
    else if ( InputButton::moveRight && !InputButton::moveLeft )
    {
        //displacement_x -= dir_x;
        //displacement_z -= dir_z;
        displacement_x -= dir_z;
        displacement_z += dir_x;
    }
    
    if ( InputButton::rotateLeft )
    {
        if ( !InputButton::rotateRight || InputButton::prioritizeRotateLeft )
        {
            globalRotateLeftAngle += ROTATION_SPEED;
        }
        else // InputButton::rotateRight && !InputButton::prioritizeRotateLeft
        {
            globalRotateLeftAngle -= ROTATION_SPEED;
        }
    }
    else if ( InputButton::rotateRight && !InputButton::rotateLeft)
    {
        globalRotateLeftAngle -= ROTATION_SPEED;
    }
    
    // if displacement has occurred, update position
    if ( displacement_x != 0.0f || displacement_z != 0.0f )
    {
        if ( displacement_x != 0.0f )
        {
            cam.translate( displacement_x * MOVEMENT_SPEED, ZERO, ZERO );
        }
        if ( displacement_z != 0.0f )
        {
            cam.translate( ZERO, ZERO, displacement_z * MOVEMENT_SPEED );
        }
        
        // Move Mobj to reticule
        maintainMobj();
    }
    
    // if rotation has occurred, update rotation
    if ( globalRotateLeftAngle != 0.0f )
    {
        OrientationMatrix3f original_cam( cam.getOrientation() );
        cam.globalRotateLeft( globalRotateLeftAngle );
        
        if ( mobj_Ptr != NULL )
            mobj_Ptr->setOrientation( cam.getOrientation() * transpose( original_cam ) * mobj_Ptr->getOrientation() );
    }
    
	// apply special keys controls
	if ( mobj_Ptr != null )
    {
		bool rotate = false;

		GL_Actorf temp_cam( cam );
		if ( InputButton::projectileRotateUp )
        {
			rotate = true;
            //rotateMasterSlave<USER_TYPE>( temp_cam, *mobj_Ptr, &GL_Actor<USER_TYPE>::localRotateUp, 0.07f );
            temp_cam.localRotateUp( 0.015f );
        }
        if ( InputButton::projectileRotateDown )
        {
			rotate = true;
            temp_cam.localRotateUp( -0.015f );
        }
        if ( InputButton::projectileRotateLeft )
        {
			rotate = true;
            temp_cam.localRotateLeft( 0.015f );
        }
        if ( InputButton::projectileRotateRight )
        {
			rotate = true;
            temp_cam.localRotateLeft( -0.015f );
        }

		if (rotate)
			mobj_Ptr->setOrientation( temp_cam.getOrientation() * transpose( cam.getOrientation() ) * mobj_Ptr->getOrientation() );
    }
 }
 inline void applyMouseControl()
 {
	OrientationMatrix3f original_cam( cam.getOrientation() );
    cam.globalRotateLeft( -mouseMoveX * ANGLE );  // Notice x extends from left to right 
												  // (so if change in x is positive/right, we rotate right)
	cam.localRotateUp( mouseMoveY * ANGLE );

    if ( mobj_Ptr != null )
        mobj_Ptr->setOrientation( cam.getOrientation() * transpose( original_cam ) * mobj_Ptr->getOrientation() );
	
	// Move Mobj to reticule
	maintainMobj();

	// Reset the mouse movement values
	mouseMoveX = 0.0f;
	mouseMoveY = 0.0f;
 }

 void WindowEntry( int state )
 {
/*
    if ( state == GLUT_ENTERED )
      isValidMouseMovement = true;
    else if ( state == GLUT_LEFT )
      isValidMouseMovement = false;
*/
    isValidMouseMovement = false;
 }

 void MouseMovePassive( int x, int y )
 {
    GLint xMove = x - mousePosition[0];	   // current_x - previous_x
    GLint yMove = -(y - mousePosition[1]);    // Notice positive y extends from the top to down the window
  
    if ( isValidMouseMovement )
    {
        // Apply rotation to the camera
        if ( xMove != 0x0 )
        {
            unsigned int xMovement( abs(xMove) );
            xMove = ( xMove < 0 ? -1 : 1 ) * MIN( xMovement, MAX_ANGLE_MOD );
			mouseMoveX += xMove;
        }

        if ( yMove != 0x0 )
        {
            unsigned int yMovement( abs(yMove) );
            yMove = ( yMove < 0 ? -1 : 1 ) * MIN( yMovement, MAX_ANGLE_MOD );
			mouseMoveY += yMove;
        }
    }

    // Update mouse pointer positions
    mousePosition[0] = ( isGameMode ? windowWidth/0x2 : x );
    mousePosition[1] = ( isGameMode ? windowHeight/0x2 : y );
    isValidMouseMovement = true;
 }
 
 void MouseClickHandler(int button, int state, int x, int y)
 {
    switch( button )
    {
        case GLUT_LEFT_BUTTON:  
            // Fire off HitBox
            if(state == GLUT_DOWN) {
            
                const Vector3f camFace = cam.getFaceAxis();
                
                if ( hasFiredHitBox )
                {
                    hasFiredHitBox = false;
                    
                    mobj_Ptr = new GL_MobileObjectf( *concave );
                    mobj_Ptr->setOrientation( cam.getOrientation() );
                    maintainMobj();
                    objects.push_back( mobj_Ptr );
                }
                else    // !hasFiredHitBox
                {
                    hasFiredHitBox = true;
                    
                    mobj_Ptr->setVelocity( Vector3f( camFace.x, camFace.y, camFace.z ) );
                    mobj_Ptr = null;
                }
            }
			break;
        case GLUT_MIDDLE_BUTTON:
			break;
        case GLUT_RIGHT_BUTTON:
			{
				// x and y extends from the top left corner of the screen
				GLint center_x;
				GLint center_y;
				center_x = (glutGet(GLUT_WINDOW_WIDTH) >> 1);
				center_y = (glutGet(GLUT_WINDOW_HEIGHT) >> 1);
				GLint change_in_x( x - center_x );
				GLint change_in_y( center_y - y );
				
				// Should change the bottom to:
				// rotation_x = convert_to_radians( (change_in_x / center_x) * (viewing_angle / 2.0f) )
				// rotation_y = convert_to_radians( (change_in_y / center_y) * (viewing_angle / 2.0f) )
				// VIEW_ANGLE

				//GLfloat radian_x( calculateAngle<GLfloat>( center_x, change_in_x ) );
				//GLfloat radian_y( calculateAngle<GLfloat>( center_y, change_in_y ) );

				//cam.globalRotateLeft( -radian_x );
				//cam.localRotateUp( radian_y );

				cam.globalRotateLeft( -(GL_PI/180.0f)*(static_cast<GLfloat>(change_in_x) / center_x) * (VIEW_ANGLE / 2.0f) );
				cam.localRotateUp( (GL_PI/180.0f)*(static_cast<GLfloat>(change_in_y) / center_y) * (VIEW_ANGLE / 2.0f) );
			}
			break;
        default:
            break;
    }
    
    glutPostRedisplay();
 }

 #undef MOVEMENT_UNIT
 #undef ANGLE
 #undef MAX_ANGLE_MOD
 #undef KEYBOARD_ANGLE

 #endif INPUT_MODULE_H